@auth.requires_login()
def index():

   return { 'sourceTrees': [ { 'id': row.id, 'name': row.name, 'creationDate': row.creationDate }
                                for row in db( ( db.tree.treeTypeId == db.treeType.id ) &
                                               ( db.treeType.name == 'source' ) ).select( db.tree.id, db.tree.name, db.tree.creationDate ) ] }


def getTree():

    tree = buildTree( { 'nodes': db( db.node.treeId == request.vars.treeId ).select( db.node.id, db.node.name, db.node.left, db.node.right, orderby = db.node.left ) } ) \
        if request.vars.type != 'saved' else getSavedClade( { 'id': request.vars.treeId } )

    return ''.join( [ '{ "id": "', request.vars.treeId,
                      '", "tree": ', tree.emit_json(),
                      ', "type": "', request.vars.type, '" }' ] )


def retrieveAllTreeInfo():
    import gluon.contrib.simplejson as json

    trees = { 'source': [ ], 'grafted': [] }

    for row in db( db.tree.treeTypeId == db.treeType.id ).select( db.tree.id, db.tree.name, db.tree.creationDate, db.treeType.name ):
        trees[row.treeType.name].append( { 'id': row.tree.id, 'name': row.tree.name, 'creationDate': row.tree.creationDate } )
        
    trees['saved'] = [ { 'id': row.id, 'name': row.name, 'creationDate': row.creationDate } \
                        for row in db( db.savedClade.id > 0 ).select( db.savedClade.id, db.savedClade.name, db.savedClade.creationDate ) ]

    return json.dumps( trees )


def graft():
    import datetime

    treeId = db.tree.insert( name = request.vars.newTreeName, creationDate = datetime.datetime.now(), treeTypeId = getTreeTypeId( { 'name': 'grafted' } ) ).id \
        if request.baseTreeType != 'grafted' else request.vars.baseTreeId

    baseTree = buildTree( { 'nodes': db( db.node.treeId == request.vars.baseTreeId ).select( db.node.id, db.node.name, db.node.left, db.node.right, orderby = db.node.left ) } ) \
        if request.vars.baseTreeType != 'saved' else getSavedClade( { 'id': request.vars.baseTreeId } )
    
    graftingTree = buildTree( { 'nodes': db( db.node.treeId == request.vars.graftingTreeId ).select( db.node.id, db.node.name, db.node.left, db.node.right, orderby = db.node.left ) } ) \
        if request.vars.graftingTreeType != 'saved' else getSavedClade( { 'id': request.vars.graftingTreeId } )

    newTree = graftTrees( { 'baseTree': baseTree, 'graftingTree': graftingTree, 'graftType': request.vars.graftType, 'nodeId': request.vars.nodeId } )

    insertNodes( { 'node': newTree, 'treeId': treeId,
                   'graftInfo': { 'graftingTreeRootNodeId': graftingTree.id,
                                  'baseTreeNodeId': request.vars.nodeId,
                                  'graftType' : request.vars.graftType } } )

    creationDate = db( db.tree.id == treeId ).select()[0].creationDate

    return ''.join( [ '{ "id": "', str(treeId),
                      '", "tree": ', newTree.emit_json(),
                      ', "creationDate": "', str(creationDate), '"',
                      ', "name": "', request.vars.newTreeName, '"',
                      ', "type": "grafted" }' ] )


def graftTrees( p ):

    f = open( '/tmp/debug', 'w' )
    f.write( str( len( p['baseTree'].descendants() ) ) + '\n' )
    
    baseTreeNode = p['baseTree'].findNodeById( int(p['nodeId']) )
    
    if( p['graftType'] == 'replacement' ):
        baseTreeNode.parent.add_child( p['graftingTree'] )
        baseTreeNode.parent.remove_child( baseTreeNode )

    else:
        baseTreeNode.add_child( p['graftingTree'] )

    assignLeftAndRightIds( { 'node': p['baseTree'], 'currentIndex': 1 } )
    
    f.write( str( len( p['baseTree'].descendants() ) ) + '\n' )

    return p['baseTree']


def assignLeftAndRightIds( p ):

    p['node'].leftId = p['currentIndex'] 

    newIndex = p['currentIndex'] + 1

    for child in p['node'].children:
        newIndex = assignLeftAndRightIds( { 'node': child, 'currentIndex': newIndex } )
         
    
    p['node'].rightId = newIndex 

    return newIndex + 1
    
     
    
def buildTree( p ):
    phylo = local_import('phylo')
    
    root = None
    currentNode = None

    for node in p['nodes']:
        if currentNode:
            while( currentNode.rightId < node.right ):
                currentNode = currentNode.parent

        treeNode = phylo.Node()
        treeNode.id = node.id
        treeNode.rightId = node.right;
        treeNode.label = node.name;

        if currentNode:
            currentNode.add_child( treeNode )
            currentNode = treeNode
        else:
            root = treeNode
            currentNode = treeNode

    return root


def getSavedClade( p ):

    rootNode = db( ( db.savedClade.id == p['id'] ) &
                   ( db.node.id == db.savedClade.nodeId ) ).select( db.node.ALL )[0]

    nodes = db( (db.node.treeId == rootNode.treeId) &
                (db.node.left >= rootNode.left) &
                (db.node.left <= rootNode.right) ).select()

    return buildTree( { 'nodes': nodes } )


def newickStringUpload():
    return addSourceTree( { 'newickString': request.vars.newickString,
                            'name': request.vars.newickName,
                            'comment': request.vars.newickComment } )


def newickFileUpload():
    return addSourceTree( { 'newickString': request.vars.newickFile.file.read(),
                            'name': request.vars.newickName,
                            'comment': request.vars.newickComment } )


def addSourceTree( p ):
    import datetime
    newick = local_import('newick')

    treeObj = None

    try:
        treeObj = newick.parse( p['newickString'] )
    except:
        return 'parseError'

    treeRow = db.tree.insert( name = p['name'], comment = p['comment'], creationDate = datetime.datetime.now(), treeTypeId = getTreeTypeId( { 'name': 'source' } ) )
    
    insertNodes( { 'node': treeObj, 'treeId': treeRow.id } )

    return response.json( { 'id': treeRow.id, 'creationDate': treeRow.creationDate, 'name': treeRow.name } )


def revertGraft():

    treeNodeIds = db( db.node.treeId == request.vars.treeId ).select( db.node.id )
    
    db( db.tree.id == request.vars.treeId ).delete()
    db( db.nodeMeta.nodeId.belongs( treeNodeIds ) ).delete()
    db( db.node.id.belongs( treeNodeIds ) ).delete()


def insertNodes( p ):
    nodeId = db.node.insert( treeId = p['treeId'], name = p['node'].label, left = p['node'].leftId, right = p['node'].rightId ).id

    for attr in [ 'length' ]:
        if hasattr( p['node'], attr ):
            db.nodeMeta.insert( nodeId = nodeId, key = attr, value = getattr( p['node'], attr ) )

    if( p['graftInfo'] ):
        db.nodeMeta.insert( nodeId = nodeId, key = 'sourceNode', value = p['node'].id )
        
        if( p['graftInfo']['graftType'] == 'replacement' and
            p['graftInfo']['graftingTreeRootNodeId'] == p['node'].id ):
                db.nodeMeta.insert( nodeId = nodeId, key = 'replacing', value = p['graftInfo']['baseTreeNodeId'] )
        
        if( p['graftInfo']['graftType'] == 'append' and
            p['graftInfo']['baseTreeNodeId'] == p['node'].id ):
                db.nodeMeta.insert( nodeId = nodeId, key = 'addedChild', value = p['graftInfo']['graftingTreeRootNodeId'] )
        

    for child in p['node'].children:
        insertNodes( { 'node': child, 'treeId': p['treeId'], 'graftInfo': p['graftInfo'] } )



def getTreeTypeId( p ):
    return db( db.treeType.name == p['name'] ).select( db.treeType.id )[0].id



def deleteTree():

    treeRow = db.tree( request.vars.treeId )
    treeNodeIds = db( db.node.treeId == request.vars.treeId ).select( db.node.id )

    if treeRow.treeTypeId == getTreeTypeId( { 'name': 'source' } ):

        nodesInGraftedTrees = db( ( ( db.nodeMeta.key == 'replacement' ) | ( db.nodeMeta.key == 'append' ) ) & db.nodeMeta.nodeId.belongs( treeNodeIds ) ).select()

        if nodesInGraftedTrees:
            return 'referenceError'
        else:
            db( db.savedClade.nodeId.belongs( treeNodeIds ) ).delete()


    db( db.tree.id == request.vars.treeId ).delete()
    db( db.nodeMeta.nodeId.belongs( treeNodeIds ) ).delete()
    db( db.node.id.belongs( treeNodeIds ) ).delete()

    return request.vars.treeId





#####################################################################################################################################
def deleteSavedClade():
    db( db.savedClade.id == request.vars.savedCladeId ).delete()
    return request.vars.savedCladeId


def viewer():

    import cPickle
 
    treeId = int(request.args[0])
 
    treeConfig = getTreeConfig( { 'treeId': treeId } )
    
    return { 'treeId': treeId,
             'config': trimTreeConfig( { 'treeConfig': treeConfig } ),
             'scaling': treeConfig.scaling,
             'supportValues': cPickle.loads( treeConfig.supportValueInfo ) }


def grafter():
    return { 'treeId': request.args[0] }







 
def retrieveTree():

    tree = getTree( { 'treeId': request.vars.id } ) if request.vars.type != 'saved' else getSavedClade( { 'id': request.vars.id } )

    return ''.join( [ '{ "id": "', request.vars.id,
                      '", "tree": ', tree.emit_json(),
                      ', "type": "', request.vars.type, '" }' ] )
    


def saveClade():
    import gluon.contrib.simplejson as json, datetime
     
    row = db.savedClade.insert( nodeId = request.vars.nodeId, name = request.vars.name, creationDate = datetime.datetime.now() )

    return json.dumps( { 'id': row.id, 'name': row.name, 'creationDate': row.creationDate, 'type': 'saved' } )



"""
def index():

    treeInfo = { 'source': [], 'piece': [], 'grafted': [] }

    if auth.is_logged_in():

        for row in db( db.sourceTree.user_id == auth.user.id ).select( db.sourceTree.id, db.sourceTree.name, db.sourceTree.creationDate ) :
            treeInfo['source'].append( { 'id': row.id, 'name': row.name, 'comment': row.creationDate } )
        
    return dict( treeInfo = treeInfo )



def saveConfig():

    for (k, v) in  session.config.items():
        if k in request.vars:
            session.config[k] = request.vars[k]


def getSavedClade( p ):

    rootNode = db( db.node.id == p['nodeId'] ).select()[0]

    nodes = db( (db.node.sourceTreeId == rootNode.sourceTreeId) &
                (db.node.left >= rootNode.left) &
                (db.node.left <= rootNode.right) ).select()

    return { 'tree': buildTree( { 'nodes': nodes } ),
             'sourceTreeId': rootNode.sourceTreeId }

def retrieveSavedClade():

    import cPickle

    savedCladeInfo = getSavedClade( { 'nodeId': request.vars.nodeId } )

    return ''.join( [ '{ "scaling": "', db( db.treeConfig.id == db.sourceTree(savedCladeInfo['sourceTreeId']).configId ).select( db.treeConfig.scaling )[0].scaling,
                      '", "id": "', request.vars.nodeId, '", "tree": ', savedCladeInfo['tree'].emit_json(), ' }' ] )


def graftClade():
    import datetime
    
    treeConfigId = insertNewTreeConfig()

    graftedTreeId = db.graftedTree.insert( sourceNode = request.vars.skeletonNodeId,
                                           graftingNode = request.vars.pieceNodeId,
                                           name = 'Untitled Tree',
                                           configId = treeConfigId,
                                           creationDate = datetime.datetime.now() ).id

    return getGraftedTree( { 'id': graftedTreeId } ).emit_json()


def getGraftedTree( p ):

    graftedTreeInfo = db.graftedTree( p['id'] )

    sourceTree = getTree( { 'treeId': db.node( graftedTreeInfo.sourceNode ).sourceTreeId } )

    graftingTree = getSavedClade( { 'nodeId': graftedTreeInfo.graftingNode } )
    
    nodeToGraft = sourceTree.findNodeById( graftedTreeInfo.sourceNode )

    if( nodeToGraft.children ):
        nodeToGraft.children.append( graftingTree )
    else:
        nodeToGraft = graftingTree

    return sourceTree




def retrieveSavedCladesInfo():

    import gluon.contrib.simplejson as json

    info = []

    for row in db( db.savedClade.id > 0 ).select():
        info.append( { 'id': row.nodeId, 'name': row.name, 'creationDate': row.creationDate } )

    return json.dumps( info )


def insertNewTreeConfig():
    return db.treeConfig.insert( collapsedNodesObj = cPickle.dumps({}), scaling = 'smooth', supportValueInfo = cPickle.dumps( { 'threshold': '', 'thickness': '' } ),
                                 visibleNodesObj = cPickle.dumps([]), cladeColor = 'black', buttonColor = 'blue', buttonRadius = '5', labelColor = 'black', labelBuffer = '0',
                                 fontFamily = 'times', fontSize = '12', nodeSeparation = '20', generationSeparation = '20', canvasBuffer = '100', pathWidth = '2', maxTips = 200 ).id

       

    
        




                                       



def getTreeConfig( p ):

    return db( (db.treeConfig.id == db.sourceTree.configId) &
               (db.sourceTree.id == p['treeId']) ).select( db.treeConfig.ALL )[0]


def saveCollapsedState( p ):

    db( db.treeConfig.id == p['configId'] ).update( collapsedNodesObj = p['collapsed'] )




 

def viewer():

    import cPickle
 
    treeId = int(request.args[0])
 
    treeConfig = getTreeConfig( { 'treeId': treeId } )
    
    return { 'treeId': treeId,
             'config': trimTreeConfig( { 'treeConfig': treeConfig } ),
             'scaling': treeConfig.scaling,
             'supportValues': cPickle.loads( treeConfig.supportValueInfo ) }
 



def trimTreeConfig( p ):
    
    return { 'cladeColor': p['treeConfig'].cladeColor, 'buttonColor': p['treeConfig'].buttonColor, 'buttonRadius': p['treeConfig'].buttonRadius,
             'labelColor': p['treeConfig'].labelColor, 'labelBuffer': p['treeConfig'].labelBuffer, 'fontFamily': p['treeConfig'].fontFamily,
             'fontSize': p['treeConfig'].fontSize, 'nodeSeparation': p['treeConfig'].nodeSeparation, 'generationSeparation': p['treeConfig'].generationSeparation,
             'canvasBuffer': p['treeConfig'].canvasBuffer, 'pathWidth': p['treeConfig'].pathWidth, 'maxTips': p['treeConfig'].maxTips };




def addSupportValueAnnotation():

    session.trees[ request.vars.treeId ]['supportValues'] = { 'threshold': request.vars.threshold,
                                                              'thickness': request.vars.thickness }

    return ','.join( [ request.vars.threshold, request.vars.thickness ] )

def removeSupportValueAnnotation():
    session.trees[ request.vars.treeId ]['supportValues'] = { 'threshold': None,
                                                              'thickness': None }


def rotate():
    
    cladeId = int(request.vars.cladeId)
    
    tree = getTree( { 'treeId': request.vars.treeId } )
    
    cladeToRotate = tree.findNodeById(cladeId)

    if request.args[0] == 'forward':
        cladeToRotate.children.insert( 0, cladeToRotate.children.pop() )
    else:
        cladeToRotate.children.append( cladeToRotate.children.pop(0) )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()



def ladderize():

    newick = local_import('newick')

    cladeId = int(request.vars.cladeId)

    tree = getTree( { 'treeId': request.vars.treeId } )

    cladeToLadderize = tree.findNodeById(cladeId)

    cladeToLadderize.order_subtrees_by_size( None, True, cladeToLadderize.isLadderized )

    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()


def mrca():
        
    newick = local_import('newick')

    nodeId = int(request.vars.nodeId)

    tree = getTree( { 'treeId': request.vars.treeId } )

    newRoot = tree.findNodeById(nodeId)
    newRoot.isRoot = True

    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': newRoot } )
    
    return newRoot.emit_json()



def pruneTree( params ):

    for id in params['nodeIds']:
        params['tree'].findNodeById(int(id)).prune()



def prune():
    

    nodeIds = request.vars.nodeIds.split(',')

    tree = getTree( { 'treeId': request.vars.treeId } )

    pruneTree( { 'tree': tree, 'nodeIds': nodeIds } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    rv = tree.emit_json()

    return rv
    

def updateTreeLabel( params ):

    nodeToUpdate = params['tree'].findNodeById( params['nodeId'] )

    if params['newLabel'] == '':
        nodeToUpdate.label = None
    else:
        nodeToUpdate.label = params['newLabel']


def editLabel():

    newick = local_import('newick')
    
    nodeId = int(request.vars.id)

    
    tree = getTree( { 'treeId': request.vars.treeId } )

    updateTreeLabel( { 'tree': tree, 'nodeId': nodeId, 'newLabel': request.vars.label } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )




def putChildrenBack( params ):

    if( hasattr( params['tree'], 'hiddenChildren' ) ):
        params['tree'].children = params['tree'].hiddenChildren

    for child in params['tree'].children:
        putChildrenBack( { 'tree': child } )



def hideChildren( params ):

    if( hasattr( params['tree'], 'hiddenChildren' ) ):
        params['tree'].children = []
    
    for child in params['tree'].children:
        hideChildren( { 'tree': child } )



def getNewick():

    import random, re
    newick = local_import('newick')

    randomInt = random.randrange(10000)
    
    tree = getTree( { 'treeId': request.vars.treeId } )

    putChildrenBack( { 'tree': tree } )

    f = open( ''.join( [ '/tmp/', str(randomInt), '.newick' ] ), 'w' )
    newickString = newick.to_string( tree )
    stripOut = re.compile( '()' )
    newickString = stripOut.sub( '', newickString )
    f.write( newickString )
    f.close()

    hideChildren( { 'tree': tree } )

    return randomInt



def save():
    import urllib,re,random
    svgFileHeader = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
    stripOut = re.compile( '<desc>.*</desc>')
    svgContent = stripOut.sub( '', urllib.unquote( request.vars.svg ) )
    randomInt = random.randrange(10000)
    f = open( ''.join( [ '/tmp/', str(randomInt), '.svg' ] ), 'w' )
    f.write( ''.join( [ svgFileHeader, svgContent ] ) )
    f.close()
    return randomInt


def receive():
    
    if request.args[0] == 'svg':
        response.headers['Content-Type'] = 'text/xml'
        response.headers["Content-Disposition"] = 'attachment;filename=svgfile.svg'
    else:
        response.headers['Content-Type'] = 'text/plain'
        response.headers["Content-Disposition"] = 'attachment;filename=newickfile.txt'

    f = open( ''.join( [ '/tmp/', request.args[1], '.', request.args[0] ] ), 'r')
    result = f.read()
    f.close()

    return result

def remove():
    import os
    os.remove( ''.join( [ '/tmp/', request.vars.id, '.', request.vars.type ] ) )





def removeCollapsed( params ):

    for child in params['node'].children:

        if( ( hasattr( child, 'hiddenChildren' ) ) & ( child.id not in params['collapsed'] ) ):
            child.isExpandable = False
            child.children = child.hiddenChildren
            del child.hiddenChildren

        elif( (child.id in params['collapsed']) & ( not hasattr( child, 'hiddenChildren' ) ) ):
            child.isExpandable = True
            child.hiddenChildren = child.children
            child.children = []
            
        removeCollapsed( { 'node' : child, 'collapsed' : params['collapsed'] } ) 



def findNode( params ):
   
    if( params['node'].id == params['nodeId'] ):
        return params['node']
    else:
        for child in params['node'].children:
            node = findNode( { 'node' : child, 'nodeId' : params['nodeId'] } )
            if node:
                return node



def updateScaling():

    session.trees[ request.vars.treeId ]['scaling'] = request.vars.scaling

    return request.vars.scaling


def expandClade():

    collapse = local_import('auto_collapse')

    cladeId = int(request.vars.cladeId)
    maxTips = int(request.vars.maxTips)
    
    tree = getTree( { 'treeId': request.vars.treeId } )

    collapsed = session.trees[ request.vars.treeId ]['collapseDict']
    visibleList = session.trees[ request.vars.treeId ]['visibleList']

    visibleList.append( cladeId )

    del collapsed[cladeId]

    expandingClade = findNode( { 'node': tree, 'nodeId': cladeId } )
    expandingClade.isExpandable = False
    expandingClade.children = expandingClade.hiddenChildren
    del expandingClade.hiddenChildren

    for child in expandingClade.children:
        if child.id in collapsed: del collapsed[child.id]
        visibleList.append( child.id )

    while (expandingClade.parent):
        if expandingClade.parent.id in collapsed: del collapsed[expandingClade.parent.id]
        visibleList.append( expandingClade.parent.id )
        expandingClade = expandingClade.parent

    visibleList = list ( set( visibleList ) )

    collapse.auto_collapse( tree, collapsed, visibleList, maxTips )

    removeCollapsed( { 'node' : tree, 'collapsed' : collapsed } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )
   
    return tree.emit_json()


def collapseClade():
    
    cladeId = int(request.vars.cladeId)

    visibleList = set(session.trees[ request.vars.treeId ]['visibleList'])
    collapsed = session.trees[ request.vars.treeId ]['collapseDict']
    collapsed[ cladeId ] = 1

    for id in visibleList:
        if id == cladeId:
            visibleList.remove(cladeId)
            break

    tree = getTree( { 'treeId': request.vars.treeId } )

    collapsingClade = tree.findNodeById( cladeId )
    collapsingClade.hiddenChildren = collapsingClade.children
    collapsingClade.children = []
    collapsingClade.isExpandable = True
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()
"""
    
def user():
    """
    exposes:
    http://..../[app]/default/user/login 
    http://..../[app]/default/user/logout
    http://..../[app]/default/user/register
    http://..../[app]/default/user/profile
    http://..../[app]/default/user/retrieve_password
    http://..../[app]/default/user/change_password
    use @auth.requires_login()
        @auth.requires_membership('group name')
        @auth.requires_permission('read','table name',record_id)
    to decorate functions that need access control
    """
    return dict(form=auth())

def download():
    """
    allows downloading of uploaded files
    http://..../[app]/default/download/[filename]
    """
    return response.download(request,db)


def call():
    """
    exposes services. for example:
    http://..../[app]/default/call/jsonrpc
    decorate with @services.jsonrpc the functions to expose
    supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
    """
    session.forget()
    return service()
